/*************************************************************************
	LazyTree
	Dynamic HTML tree, with support for lazy loading of branches.

	This code may be used freely, as long as 
	- you accept, that NO WARRANTY is given: 
	  use it a your own risk OR DON'T USE IT AT ALL
	- this comment header is left intact

	A current version and some documentation should be available at 
		http://www.wwWendt.de/ref/lazytree/

	Let me know, if you find bugs or improvements.
	(c) 2006 martin@wwWendt.de
 *************************************************************************/

/************************************************************************* *	Common functions for extending classes etc. *	Borrowed from prototype.js */var Class = {	create: function() {		return function() {			this.initialize.apply(this, arguments);		}	}}/*************************************************************************
 *	Debug funcions
 */
var bDebug = true;
var divLogPanel = null;
function ltLog (msg) {
	if ( !bDebug  )
		return;
	if ( !divLogPanel ) {
		divLogPanel = document.getElementById('idLogPanel');
		if ( !divLogPanel ) {
			bDebug = false;
			return;
		}
	}
	var dt = new Date();
	divLogPanel .innerHTML += dt.getHours()+':'+dt.getMinutes()+':'+dt.getSeconds()+'.'+dt.getMilliseconds()
	 + '  ' + msg + '<br>';
}
 
/*************************************************************************
 *	CLazyTreeNode
 */
var LTNodeStatus_Error   = 'Error.';
var LTNodeStatus_Reading = 'Loading...';
var LTNodeStatus_Ok      = null;

var CLazyTreeNode = Class.create();
CLazyTreeNode.prototype = {
	initialize: function(tree, data) {
		this.tree    = tree;
		if ( typeof data == 'string' ) {
    		this.data    = null;
	    	this.title   = data;
		} else {
    		this.data    = data;
    		this.title   = data.title;
		}
		this.parent  = null; // not yet added to a parent
		this.div     = null; // not yet created
		this.span    = null; // not yet created
		this.aChilds = null; // new Array();
		this.bIsFolder   = false;
		this.bDelayed    = false;
		this.bRead       = false;
		this.bExpanded   = false;
	},
	inspect: function() {
		return '"' + this.title + '" (' + this.data + ')';
	},
	getInnerHtml: function() {
		// cache tags
		var ip = this.tree.imagePath; //'images/';

		this.tagFld    = '<img src="' + ip + 'ltFld.gif" alt="" height="16" width="16"/>';
		this.tagFld_o  = '<img src="' + ip + 'ltFld_o.gif" alt="" height="16" width="16"/>';
		this.tagDoc    = '<img src="' + ip + 'ltDoc.gif" alt="" height="16" width="16"/>';
		this.tagL_ns   = '<img src="' + ip + 'ltL_ns.gif" alt="|" height="16" width="16"/>';
		this.tagL_     = '<img src="' + ip + 'ltL_.gif" alt="" height="16" width="16"/>';

		var res = '';
		
		// parent connectors
		var bIsRoot = (this.parent==null);
		var bHideFirstConnector = ( !this.tree.bRootVisible || !this.tree.bRootCollapsible );
		
		var p = this.parent;
		while ( p ) {
			if ( ! (bHideFirstConnector && p.parent==null ) )
				res = ( p.isLastSibling() ? this.tagL_ : this.tagL_ns) + res ;
			p = p.parent;
		}
		
		// connector (expanded, expandable or simple
//		alert (this.title + ' isLast=' + this.isLastSibling());
		var imgConnector = null;
		var imgAlt = '';
		var bHasLink = true;
		if ( bHideFirstConnector && bIsRoot  ) {
			// skip connector
			imgConnector = null;
			bHasLink = false;
		} else if ( this.aChilds && this.bExpanded  ) {
			imgConnector = ( this.isLastSibling() ? 'ltM_ne' : 'ltM_nes' );
			imgAlt = '-';
		} else if (this.aChilds) {
			imgConnector = ( this.isLastSibling() ? 'ltP_ne' : 'ltP_nes' );
			imgAlt = '+';
		} else if (this.bDelayed) {
			imgConnector = ( this.isLastSibling() ? 'ltD_ne' : 'ltD_nes' );
			imgAlt = '?';
		} else {
			imgConnector = ( this.isLastSibling() ? 'ltL_ne' : 'ltL_nes' );
			bHasLink = false;
		}

//		var anchor = '#' + this.key;
		if ( bHasLink )
			res += '<a href="#" onClick="parentNode.ltn.expand();">';
//			res += '<a href="javascript:parentNode.ltn.expand();">'
		if ( imgConnector )
			res += '<img src="' + this.tree.imagePath + imgConnector + '.gif" alt="' + imgAlt + '" height="16" width="16" />'
		if ( bHasLink )
			res += '</a>';

		// folder or doctype icon
//		res += ( this.bIsFolder ? this.tagFld : this.tagDoc );
   		if ( this.data && this.data.icon ) {
    		res += '<img src="' + ip + this.data.icon + '" alt="" height="16" width="16"/>';
		} else if ( this.bIsFolder ) {
	    	res += ( this.bExpanded ? this.tagFld_o : this.tagFld );
		} else {
	    	res += this.tagDoc;
		}
		res += '&nbsp;';

		// node name
		var hint = ( this.data && typeof this.data.hint == 'string' ) ? ' title="' + this.data.hint + '"' : '';
		if ( this.bIsFolder && this.tree.bSelectExpandsFolders && (this.aChilds || this.bDelayed) ) {
			res +=  '<a href="#" onClick="parentNode.ltn.expand();"' + hint + '>' + this.title + '</a>';
		} else if ( !this.bIsFolder ) {
			res +=  '<a href="#" onClick="parentNode.ltn.select();"' + hint + '>' + this.title + '</a>';
		} else {
			res +=  this.title;
		}
/*		
		res +=  (this.bSelectable ? '<a href="#" onClick="parentNode.ltn.select();">' : '')
		+ this.title
		+ (this.bSelectable ? '</a>' : '');
*/
		return res;
	},
	_createOrSetDomElement: function() {
		if ( this.div==null ) {
			this.div  = document.createElement ('div');
			this.div.className = 'tnLevelN';

			this.span = document.createElement ('span');
			this.span.className = 'ltNode';
			this.span.ltn = this;
			this.div.appendChild ( this.span );
		} else {
			// simply replace existing span's inner html
		}
		return this.div;
	},
	render: function (bDeep, bHidden) {
		
//		ltLog ('render '+this.title+', expanded='+this.bExpanded + ', aChilds='+(this.aChilds?this.aChilds.length:'0'));
		// --- create <div><span>..</span></div> tags for this node
		if ( ! this.span ) {
			this._createOrSetDomElement();
			if ( this.parent ) 
				this.parent.div.appendChild ( this.div );
			this.span.className = ( this.bIsFolder ? 'ltFolder' : 'ltDocument' );
		}
		// hide root?
		if ( this.parent==null )
			this.span.style.display = ( this.tree.bRootVisible ? '' : 'none');
		// hide this node, if parent is collapsed
		this.div.style.display = ( this.parent==null || this.parent.bExpanded ? '' : 'none');

		// set node connector images, links and text
		this.span.innerHTML = this.getInnerHtml();
		
		if ( bDeep && (this.aChilds != null ) && (bHidden || this.bExpanded) ) {
			for (var i=0; i<this.aChilds.length; i++) {
				this.aChilds[i].render (bDeep, bHidden)
			}
		}
	},
	isLastSibling: function() {
		var p = this.parent;
		if ( !p ) return true;
		return p.aChilds[p.aChilds.length-1] == this;
	},
	select: function() {
		this.tree.tnSelected = this;
		if ( this.tree.onSelect )
			this.tree.onSelect (this);
	},
	setLazyNodeStatus: function (lts) {
		var firstChild = ( this.aChilds ? this.aChilds[0] : null );
		if ( lts==LTNodeStatus_Ok ) {
			if ( firstChild ) {
				this.div.removeChild (firstChild.div);
			}
			this.bRead = true;
		} else {
			if ( firstChild ) {
				firstChild.title = lts;
				firstChild.render (false, false);
			} else {
				this._addChildNode (new CLazyTreeNode (this.tree, lts, null));
			}
		}
	},
	_expand: function (bExpand) {
		if ( this.bExpanded == bExpand )
			return;
//		ltLog ('_expand ('+this.title+', '+bExpand+')...');
		this.bExpanded = bExpand;
		// expanding a lazy node: set 'loading...' and call callback
		if ( bExpand && this.bDelayed && !this.bRead ) {
			if ( this.tree.onLazyRead ) {
				this.setLazyNodeStatus ('reading');
				this.tree.onLazyRead (this);
			} else {
				alert ('onLazyRead callback not set');
			}
			return;
		}
		// render expanded nodes
		this.render (true, false);
		// we didn't render collapsed nodes, so we have to update the visibility of direct childs
		if ( this.aChilds ) {
			for (var i=0; i<this.aChilds.length; i++) {
				this.aChilds[i].div.style.display = (this.bExpanded ? '' : 'none');
			}
		}
	},
	expand: function() {
		ltLog ('expand ('+this.title+')...');
		this._expand ( ! this.bExpanded);
		// auto-collapse mode
		if ( this.bExpanded && this.parent && this.tree.bAutoCollapse ) {
			var ac = this.parent.aChilds;
			for (var i=0; i<ac.length; i++) {
//				ltLog ('aci=('+ac[i].title+', this='+this.title+')...' + ', eq=' + (ac[i] == this));
				if ( ac[i]!=this )
					ac[i]._expand (false);
			}
		}
		ltLog ('expand ('+this.title+') done.');
	},
/*	
	onLoad: function() {
		alert ('expand ' + this.key);		
	},
	_cbHide: function (tn) {
		tn.div.style.display = 'none';
	},
	_cbShow: function (tn) {
		tn.div.style.display = '';
	},
	showChilds: function (bShow) {
		// don't recurse, because div tags are nested anyway
		this.visit (bShow ? this._cbShow : this._cbHide, false, false); 
	},
	visit: function (cb, bSelf, bDeep) {
		var n = 0;
		if ( bSelf ) { cb (this); n++; }
		if ( this.aChilds ) {
			for (var i=0; i<this.aChilds.length; i++) {
				if ( bDeep ) {
					n += this.aChilds[i].visit (cb, true, bDeep);
				} else {
					cb (this.aChilds[i]);
					n++;
				}
			}
		}
		return n;
	},
*/
	_addChildNode: function (tn) {
//		ltLog ('addChild '+tn.title);
		if ( this.aChilds==null ) {
			this.aChilds = new Array();
			this.eExpandable = 1;
		}
		this.aChilds.push (tn);
		tn.parent = this;
		
		if ( this.tree.bExpandOnAdd || ( (!this.tree.bRootCollapsible || !this.tree.bRootVisible) && this.parent==null ) )
			this.bExpanded = true;
		if ( this.tree.bEnableUpdate )
			this.render (true, false);
		return tn;
	},
	collapseSiblings: function() {
		if ( this.parent==null )
			return;
		var ac = this.parent.aChilds;
		for (var i=0; i<ac.length; i++) {
			if ( ac[i]!=this && ac[i].bExpanded )
				ac[i].expand();
		}
	},
	addDoc: function (data) {
		var tn = new CLazyTreeNode (this.tree, data);
		return this._addChildNode (tn);
	},
	addFolder: function (data) {
		var tn = new CLazyTreeNode (this.tree, data);
		tn.bIsFolder = true;
		return this._addChildNode (tn);
	},
	addLazyDoc: function (data) {
		var tn = new CLazyTreeNode (this.tree, data);
		tn.bDelayed  = true;
		tn.bRead     = false;
		return this._addChildNode (tn);
	},
	addLazyFolder: function (data) {
		var tn = new CLazyTreeNode (this.tree, data);
		tn.bIsFolder = true;
		tn.bDelayed  = true;
		tn.bRead     = false;
		return this._addChildNode (tn);
	},
	// --- end of class
	lastFunction: function ()
	{}
}

/*************************************************************************
 * CLazyTree
 */
var CLazyTree = Class.create();
CLazyTree.prototype = {
	// static members
	version: '0.2',
	// constructor
	initialize: function (id, name) {
		ltLog ('CLazyTree.initialize()');
		// instance members
		this.bEnableUpdate = true;
		this.tnSelected    = null;
		this.imagePath     = 'skin/';
		this.onSelect      = null;
		this.onLazyRead    = null;

		this.bRootVisible     = true;
		this.bRootCollapsible = false;
		this.bExpandOnAdd     = false;
		this.bSelectExpandsFolders = true;
		this.bAutoCollapse    = false;
		
		// find container element
		this.divTree   = document.getElementById (id);
		// create the root element
		this.tnRoot    = new CLazyTreeNode (this, {title:name, key:'root'});
		this.tnRoot.bIsFolder   = true;
		this.tnRoot.render (false, false);
		this.divRoot   = this.tnRoot.div;
		this.divRoot.className = 'lazyTree';
		// add root to container
		this.divTree.appendChild (this.divRoot);
	},
	// member functions
	inspect: function() {
		return 'CLazyTree "' + name + '"';
	},
	redraw: function() {
		ltLog ('redraw... ');
		this.tnRoot.render (true, false);
		ltLog ('redraw done. ');
	},
	getRoot: function (tn) {
		return this.tnRoot;
	},
	enableUpdate: function (bEnable) {
		if ( this.bEnableUpdate==bEnable )
			return bEnable;
		this.bEnableUpdate = bEnable;
		if ( bEnable )
			this.redraw();
		return !bEnable; // return prev. value
	},
	// --- end of class
	lastFunction: function ()
	{}
}
